﻿namespace AgentJohnson.Generators
{
  using System.Collections.Generic;
  using System.Linq;
  using Resharper.Abstractions.CodeDom;
  using Resharper.Abstractions.CodeDom.Accessors;
  using Resharper.Abstractions.CodeDom.Statements.Blocks;
  using Resharper.Abstractions.CodeDom.TreeNodes;
  using Resharper.Abstractions.CodeDom.TypeMembers.Methods;
  using Resharper.Abstractions.Generators;
  using Resharper.Abstractions.Shell;

  /// <summary>Defines the <see cref="ReturnPopulator"/> class.</summary>
  public class ReturnPopulator : IGeneratorPopulator
  {
    #region Implemented Interfaces

    #region IGeneratorPopulator

    /// <summary>Populates the specified data context.</summary>
    /// <param name="context">The data context.</param>
    /// <returns>Returns the I enumerable.</returns>
    public IEnumerable<GeneratorBase> Populate(DataContext context)
    {
      var model = this.GetModel(context);
      if (model == null)
      {
        yield break;
      }

      if (model.TypeName == "bool")
      {
        yield return new ReturnGenerator(model, "return true", "return true;");
        yield return new ReturnGenerator(model, "return false", "return false;");
      }
      else if (model.TypeName == "void")
      {
        yield return new ReturnGenerator(model, "return", "return;");
      }
      else if (model.TypeName == "string")
      {
        yield return new ReturnGenerator(model, "return string.Empty", "return string.Empty;");
        yield return new ReturnGenerator(model, "return <variable>", "return $result$;", "result", "completeSmart()");
        yield return new ReturnGenerator(model, "return null", "return null;");
      }
      else
      {
        yield return new ReturnGenerator(model, "return null", "return null;");
        yield return new ReturnGenerator(model, "return <variable>", "return $result$;", "result", "completeSmart()");
      }
    }

    #endregion

    #endregion

    #region Methods

    /// <summary>Gets the model.</summary>
    /// <param name="context">The context.</param>
    /// <returns>Returns the model.</returns>
    private Model GetModel(DataContext context)
    {
      var block = context.GetContainingBlock();
      if (block == null)
      {
        return null;
      }

      var treeNode = context.TreeNode;
      if (treeNode == null)
      {
        return null;
      }

      if (!this.IsAfterLastStatement(block, treeNode))
      {
        return null;
      }

      Type returnType = null;
      
      var method = block.GetContainingMethod();
      if (method != null)
      {
        returnType = method.ReturnType;
      }

      var accessor = block.GetContainingAccessor();
      if (accessor != null)
      {
        returnType = accessor.ReturnType;
      }

      if (returnType == null)
      {
        return null;
      }

      if (returnType.ToString() != "bool" && !returnType.IsVoid && !returnType.IsReferenceType)
      {
        return null;
      }

      var typeName = returnType.ToString();

      return new Model
      {
        TypeName = typeName
      };
    }

    /// <summary>Determines whether [is after last statement] [the specified block].</summary>
    /// <param name="block">The block.</param>
    /// <param name="treeNode">The tree node.</param>
    /// <returns><c>true</c> if [is after last statement] [the specified block]; otherwise, <c>false</c>.</returns>
    private bool IsAfterLastStatement(Block block, TreeNode treeNode)
    {
      var statement = block.Statements.LastOrDefault();
      if (statement == null)
      {
        return true;
      }

      return statement.GetRange().End <= treeNode.GetRange().End;
    }

    #endregion

    /// <summary>Defines the <see cref="Model"/> class.</summary>
    public class Model
    {
      #region Properties

      /// <summary>
      /// Gets or sets the name of the type.
      /// </summary>
      /// <value>The name of the type.</value>
      public string TypeName { get; set; }

      #endregion
    }
  }
}